package com.college.common.utils.security;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class YxAesUtils {


    private static final String KEY_ALGORITHM = "RSA";

    private static final String SIGNATURE_ALGORITHM = "SHA256WithRSA";

    /**
     * 加密块大小 - 如果内容大于117字节需要分段加密
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLVnfnyWif40j81B9xW7WaqU9rZggAYcf8b6I2UGE9wtakRbn6Wo5qgaZBueUpYkYA5xtRIMBD4ATlowCVAX/HcEVqP7ZyvHtJz9BNMLpy/NngtS9SDh9dszdQFKfQOk02E3MdzkM481zhTjr3LjJvLjlz2OdeNqFB5YKiWVyqAQIDAQAB";

    public static final String PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAINPaThOdLd5S0U+K9dFtdVSAKd+mrb9insLQLLGqBXSTvRQrTtwXlGrWq/xVFI9aGVsMPrDysFUXsX+/BR/wHnhaZdm8Qkl8+BLoMviUko01qu+qOnaklBlDYGirkRMUPo9QBG3c1BkZks+DVaWVFbOjnkGA6ewO82SZdTqNLm3AgMBAAECgYAzLI6gMAtSucW47ZreYb+UrEmu/StEp5kBjQgg5ET7hCPbGVn58TgA8hv8t7uZ9W6rIDgScp2hLMVmupH5VzRmfKM2dvFYN4sAb2NF6C4V9AUnZI/ar6uJ/yC57HOaBu1eiHVVieOZHVTN1Qv81fzEhoRRZ8tozDi/vJCOHWOWqQJBALob4vLn0LHuFb+D4Y7cjgzN0M7hWTbm+M4UO5WI1Wh72vbBF7v0RfO6A34n/KaHEaZXlTNkSPVB4PUj84J1eFsCQQC0n0q6YUq5+L3ItJysKhbdR/8iBdsn5Ev3XmBKQHXuUA0GaN2VsLSIaJKVY8YbNLGBArhxDz3wQOsWbwcaSqLVAkB4PO4KKzTMz9ojKmw+pUXtBcfC8Rp3exAB7FZ2jLn70BEuXKAd4XqUNXhuOB1l5VvE0U7mrgLo2VAicMCxZ32DAkEAgxfyFuAyalP8+sULyXEq8yQkKr9xVry93iZjYMD0VylGqtZFrTV5cl/jtAWcbXaLNoWQ9I4JeGzFs5yV3SE6mQJAaSWGFKs6wKnHWog9rbKINKpKm4GYNsRNlBKHN0rhwiXweVKAWNsXnZHfPWIPm0SJQH465J7DZyJ//YvGM177Cw==";

    /**
     * 创建RSA公钥和私钥对
     * <p>
     * 公钥：RSAUtils.PUBLIC_KEY
     * 私钥：RSAUtils.PRIVATE_KEY
     *
     * @throws NoSuchAlgorithmException 创建异常
     */
    private static void newRsaKeys() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        String publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded());
        String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded());
        System.out.println("RSA::PrivateKey::" + privateKeyStr);
        System.out.println("RSA::PublicKey::" + publicKeyStr);
    }

    public static void main(String[] args) throws Exception {
//        newRsaKeys();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("status", true);
        jsonObject.put("msgCode", "0000");
        jsonObject.put("msgDesc", "可准入");

        String longdept = "1688376891210";
        // 加密
        System.out.println(encrypt("173", Long.valueOf(longdept), jsonObject.toString(), PUBLIC_KEY, PRIVATE_KEY));
        String name = "{\"sign\":\"OgOdZgsEzb2cXhe/n6F9rltLl2wfIzLT53zG0w6xoZwv3asx/1xTPakpUYpAtd8cy32hs/Cz4z3rpI4KCjQaxZFSE9zcG0s5zgYwp2NZAzOTLrDcbptS1fEk8ogDSoaEKXDqvD/klFL9NUGyVp5TAuIPzciTpwrnhfAJOwn7qn4=\",\"t\":\"1688376891210\",\"params\":\"S/BPJzrYIroDP7NiLjuELRQ83ZeeEiQJ7q7gQlBQq8BQJwqZu4yTDJUe8getkfjR1f70lAseTyx6+M0v/cHLeQ==\",\"channelId\":\"173\",\"key\":\"Ls/N0SikRqjmPsD+RXZrn+7pDFKUTQ98CmGV4eMkXx+UR+n5h+3Jm91U1/9/svoMqM2fOG/1R3Low6tGg0CAedieEMmi1iXtjnKzEkZpAUw62281xCa0NWI3uf1VloIv7wpW8UGxLs8ZdrvPYWQhfLaeIEFIXFZTOWEzsQMkSXE=\"}";
        JSONObject jsonObject1 = JSONObject.parseObject(name);
        // 解密

        System.out.println("解密参数"+decrypt(jsonObject1, jsonObject1.getString("sign"),
                jsonObject1.getString("key"),
                jsonObject1.getString("params"), "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDT2k4TnS3eUtFPivXRbXVUgCnfpq2/Yp7C0CyxqgV0k70UK07cF5Rq1qv8VRSPWhlbDD6w8rBVF7F/vwUf8B54WmXZvEJJfPgS6DL4lJKNNarvqjp2pJQZQ2Boq5ETFD6PUARt3NQZGZLPg1WllRWzo55BgOnsDvNkmXU6jS5twIDAQAB", "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIeyGRaOwyTtna+3Z9+VEOAi5qeW1yOZbYMkgJcAXvLJg1NaQZrH2eKcL0Lzgwfm4dWQLS5QpvewUMrMf4uim43IrTgUoPFumRj1Ebkx1uixUKbetQi4E4OYZVFMtdWv1lwz4B72f9bA573QbxYYCQrVn5TYgPzdmaUHNO/JCHEdAgMBAAECgYBbC4ARxo/ggx29/EzRku7TtBcWcvq+g/8acq9koXcTZ2mZz4EdHOJwTWLYhRJxhtQXurjVzn94yPAsYbFiOyQqrTV2bcnQ528Li8vQ2Msa1wOCNCD+7mAIOOt6maXEt2mbRSzPuEOmXQcY0CSoTCtxSxRTf+Hmk53kV2Ef6z6uAQJBANQ+gYqb3wt1+U39HM+41nwPgqvurBtFAShDWBA8702n2kJV5PPRrktvL3xvTASbesB3kemH9CtXPQzltnC8wn0CQQCjq6Kn62qjyKJZX6mzQGYrq2suTuzn5EqisRQOkgNwg2X7dcBrgFAXH+O6XNgFM2fB2Ol0qI+pgCRGAj1NOAshAkAfz+qcuvY4gotGjDyoSd0kJTMB0DlNsQ8WWNm3PVrk+da2AKfbf78vIVAtx4TOX/uLI6DkLvODF4VYDIpG5ZQFAkA/Uij0xEyWnwnIgA08j1636HLoEJrxQE5r0QyMcosoHX34Il9Wd+cEe070i/T7Vh7/TTdM7eXe0ve1qiojVNphAkEAiXHws1zMBvaV2VJUZQN+PG0ah8cPE2emKWocgB0WEnIW6M9qTUXTA+7+dEzSeqV4d3HsUClwT6JXflecGYhntQ=="));

    }

    //====================== 渠道方请求宜信接口 加解密方法 ===========

    /**
     * 加密 请求数据 及 加签
     *
     * @param channel       渠道方名称
     * @param t             系统时间
     * @param data          加密前的业务数据
     * @param rsaPublicKey  rsa公钥
     * @param rsaPrivateKey rsa私钥
     * @return 加密后的请求数据
     */
    public static String encrypt(String channel, Long t, String data, String rsaPublicKey, String rsaPrivateKey) {
        // 随机字符串 作为 “AES秘钥” (注意AES加解密128/192/256 bits.对应秘钥16/24/32位)
        String aesKey = RandomStringUtils.randomAlphanumeric(16);
        String params, key;
        try {
            // 使用AES秘钥加密得到data
            params = aesEncrypt(data, aesKey);
            // 使用RSA公钥 加密 “AES秘钥” 得到 key
            key = rsaEncryptWithPublicKeyToBase64(getPublicKey(rsaPublicKey), aesKey.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new RuntimeException("加密失败", e);
        }
        // 组装数据
        Map<String, String> request = new HashMap<>(5, 1F);
        request.put("channelId", channel);
        request.put("t", String.valueOf(t));
        request.put("params", params);
        request.put("key", key);
        // 加签
        String signContent = getSignContent(request);
        String sign = signByPrivateKey(signContent, rsaPrivateKey);
        request.put("sign", sign);

        return JSON.toJSONString(request);
    }


    /**
     * 验签 及 解密 响应数据
     *
     * @param response      响应数据
     * @param sign          签名, 响应数据中的sign字段
     * @param key           aes密钥, 请求体中的key字段
     * @param data          密文业务数据, 响应数据中的data字段
     * @param rsaPublicKey  rsa公钥
     * @param rsaPrivateKey rsa私钥
     * @return 解密后的业务数据
     */
    public static String decrypt(Object response, String sign, String key, String data,
                                 String rsaPublicKey, String rsaPrivateKey) {
        // 验签
        String signContent = getSignContent(JSON.parseObject(JSON.toJSONString(response),
                new TypeReference<Map<String, String>>() {
                }));
        boolean isSignCorrect = verifySignByPublicKey(signContent, sign, rsaPublicKey);
        if (!isSignCorrect) {
            throw new RuntimeException("验签失败");
        }

        try {
            // 使用私钥解密得到AES秘钥
            String aesKey = decryptByPrivateKey(key, rsaPrivateKey);
            // 使用AES秘钥解密params得到业务参数
            return aesDecrypt(data, aesKey);
        } catch (Exception e) {
            throw new RuntimeException("解密失败", e);
        }
    }

    //====================== 宜信请求渠道方接口 加解密方法 ===========

    /**
     * 验签 及 解密请求数据
     *
     * @param request       请求数据
     * @param sign          签名, 请求数据中的sign字段
     * @param key           aes密钥, 请求数据中的key字段
     * @param params        密文业务数据, 请求数据中的params字段
     * @param rsaPublicKey  rsa公钥
     * @param rsaPrivateKey rsa私钥
     * @return 解密后的业务数据
     */
    public static String decryptRequest(Object request, String sign, String key, String params,
                                        String rsaPublicKey, String rsaPrivateKey) {
        // 验签
        String signContent = getSignContent(JSON.parseObject(JSON.toJSONString(request),
                new TypeReference<Map<String, String>>() {
                }));
        boolean isSignCorrect = verifySignByPublicKey(signContent, sign, rsaPublicKey);
        if (!isSignCorrect) {
            throw new RuntimeException("验签失败");
        }

        try {
            // 使用私钥解密得到AES秘钥
            String aesKey = decryptByPrivateKey(key, rsaPrivateKey);
            // 使用AES秘钥解密params得到业务参数
            return aesDecrypt(params, aesKey);
        } catch (Exception e) {
            throw new RuntimeException("解密失败", e);
        }
    }

    /**
     * 加密 响应数据 及 加签
     *
     * @param code          返回码, 响应体中的code字段
     * @param msg           响应信息, 响应体中的msg字段
     * @param responseData  加密前的业务数据
     * @param rsaPublicKey  rsa公钥
     * @param rsaPrivateKey rsa私钥
     * @return 加密后的响应数据
     */
    public static String encryptResponse(String code, String msg, String responseData,
                                         String rsaPublicKey, String rsaPrivateKey) {
        // 随机字符串 作为 “AES秘钥” (注意AES加解密128/192/256 bits.对应秘钥16/24/32位)
        String aesKey = RandomStringUtils.randomAlphanumeric(16);
        String data, key;
        try {
            // 使用AES秘钥加密得到data
            data = aesEncrypt(responseData, aesKey);
            // 使用RSA公钥 加密 “AES秘钥” 得到 key
            key = rsaEncryptWithPublicKeyToBase64(getPublicKey(rsaPublicKey), aesKey.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new RuntimeException("加密失败", e);
        }
        // 组装数据
        Map<String, String> response = new HashMap<>(5, 1F);
        response.put("code", code);
        response.put("msg", msg);
        response.put("data", data);
        response.put("key", key);
        // 加签
        String signContent = getSignContent(response);
        String sign = signByPrivateKey(signContent, rsaPrivateKey);
        response.put("sign", sign);

        return JSON.toJSONString(response);
    }


    /**
     * 对签名转换升序排序
     */
    private static String getSignContent(Map<String, String> paramObj) {
        TreeMap<String, String> treeMap = convertMapToTreeMap(paramObj);
        return treeMap.entrySet().stream()
                .map(entry -> {
                    if (!"sign".equals(entry.getKey())) {
                        if (StringUtils.isNotEmpty(entry.getValue()) && !"null".equalsIgnoreCase(entry.getValue())) {
                            return entry.getKey() + "=" + entry.getValue();
                        }
                    }
                    return "";
                })
                .collect(Collectors.joining("&"));
    }

    private static TreeMap<String, String> convertMapToTreeMap(Map<String, String> paramMap) {
        TreeMap<String, String> treeMap = new TreeMap<>();
        if (paramMap == null) {
            return treeMap;
        }
        treeMap.putAll(paramMap);
        // 本身的签名信息不参与签名
        treeMap.remove("sign");
        return treeMap;
    }

    private static String signByPrivateKey(String content, String privateKey) {
        try {
            PrivateKey priKey = getPrivateKey(privateKey);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(priKey);
            signature.update(content.getBytes(StandardCharsets.UTF_8));
            byte[] signed = signature.sign();
            return new String(Base64.encodeBase64(signed), StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("加签失败");
        }
    }

    private static boolean verifySignByPublicKey(String content, String sign, String publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            byte[] encodedKey = Base64.decodeBase64(publicKey.getBytes(StandardCharsets.UTF_8));
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(pubKey);
            signature.update(content.getBytes(StandardCharsets.UTF_8));
            return signature.verify(Base64.decodeBase64(sign.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            throw new RuntimeException("验签失败");
        }
    }

    /**
     * 获取私钥
     */
    private static PrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 获取公钥
     */
    private static PublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        return keyFactory.generatePublic(keySpec);
    }

    private static String aesEncrypt(String content, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"));
        byte[] bytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
        return Base64.encodeBase64String(bytes);
    }

    private static String rsaEncryptWithPublicKeyToBase64(PublicKey publicKey, byte[] rawData) throws Exception {
        final byte[] encryptBytes = encryptBytes(publicKey, rawData, Cipher.ENCRYPT_MODE);
        return Base64.encodeBase64String(encryptBytes);
    }

    private static byte[] encryptBytes(Key key, byte[] rawData, int encryptMode) throws Exception {
        String algorithm = key.getAlgorithm();
        final Cipher cipher = Cipher.getInstance(algorithm.toUpperCase());
        cipher.init(encryptMode, key);
        return cipher.doFinal(rawData);
    }

    private static String decryptByPrivateKey(String value, String key) throws Exception {
        byte[] data = Base64.decodeBase64(value);
        // 对私钥解密
        byte[] keyBytes = Base64.decodeBase64(key);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        // 对数据解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(doFinalBySegment(cipher, data, false), StandardCharsets.UTF_8);
    }

    private static byte[] doFinalBySegment(Cipher cipher, byte[] source, boolean isEncode) throws Exception {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            int blockSize = isEncode ? MAX_ENCRYPT_BLOCK : MAX_DECRYPT_BLOCK;
            if (source.length <= blockSize) {
                return cipher.doFinal(source);
            }
            int offsetIndex = 0, offset = 0, sourceLength = source.length;
            while (sourceLength - offset > 0) {
                int size = Math.min(sourceLength - offset, blockSize);
                byte[] buffer = cipher.doFinal(source, offset, size);
                out.write(buffer, 0, buffer.length);
                offsetIndex++;
                offset = offsetIndex * blockSize;
            }
            return out.toByteArray();
        }
    }

    /**
     * aes解密
     *
     * @param encryptBytes 密文
     * @param key          秘钥
     */
    private static String aesDecrypt(String encryptBytes, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"));
        byte[] decryptBytes = cipher.doFinal(Base64.decodeBase64(encryptBytes));
        return new String(decryptBytes);
    }
}
